package com.jgy.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jgy.annotation.RequestLimit;
import com.jgy.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 姜光远
 * @date 2023/4/7 14:42
 */
@Aspect
@Component
@Slf4j
public class RequestAop {
    //当同一用户、相等请求方法、相同URI、相同参数重复请求时
    private final String resultObject="请求繁忙！";

    @Autowired
    RedissonClient redissonClient;

    @Pointcut("execution(* com.jgy.controller..*.*(..))")
    public void pointcut() {
    }
    @Pointcut("@annotation(com.jgy.annotation.RequestLimit)")
    public void pointcutAn(){
    }

    @Around(value = "pointcutAn()")
    public R around(ProceedingJoinPoint joinPoint) throws  Throwable{
        //获取被调用的方法签名
        MethodSignature methodSignature= (MethodSignature) joinPoint.getSignature();
        //获取被调用的方法
        Method method=methodSignature.getMethod();
        //获取该方法上面的@RequestLimit注解
        RequestLimit requestLimit = method.getAnnotation(RequestLimit.class);
        RLock lock = null;
        R result=null;
        //如果该方法上面有这个注解，并且没有主动关闭接口幂等性
        if(requestLimit != null && requestLimit.enable()){
            //获取当前进程的ServletRequest对象
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            //获取请求参数名
            String[] params = methodSignature.getParameterNames();
            //获取请求参数值
            Object[] args = joinPoint.getArgs();
            //将参数名和参数值转成Map，且一一对应
            Map<String, Object> maps = new HashMap<>();
            for(int i=0; i<params.length; i++){
                maps.put(params[i], args[i]);
            }
            //根据 客户端IP + http请求方式 + 请求的URI + 参数的摘要值 来计算锁名
            String lockName=getIpAddr(request)+'|'+request.getMethod()+'|'+request.getRequestURI()+'|'+md5Encrypt(maps);
            log.info("locaName:{}",lockName);
            lock = redissonClient.getLock(lockName);
            //从redis中尝试再20ms内获取该锁，若是超过20ms也没获取到，则放弃，并返回false，如果获取到，则返回true，并自动上锁
            if(!lock.tryLock(10, TimeUnit.MILLISECONDS)){
                log.info("系统繁忙");
                //没有获取到，表名接口被重复调用
                return R.failed().setMsg(resultObject);
            }
        }
        //获取到锁
        try {
            log.info("------------------------执行开始------------------------");
            lock.lock();
            result = (R) joinPoint.proceed();
        } finally {
            //如果获取到锁，且处于上锁状态，已经执行完毕，释放锁
            //if(lock != null && lock.isLocked()) {
                lock.unlock();
            //}
            log .info("------------------------执行结束------------------------");
        }
        return R.ok(result);
    }



    //将Map转为Json字符串，再根据Json字符串计算摘要值，并返回，用于判断是不是相同参数的请求重复提交了
    public String md5Encrypt(Map<String, Object> maps) throws JsonProcessingException, NoSuchAlgorithmException {
        ObjectMapper objectMapper = new ObjectMapper();
        String json = null;
        json = objectMapper.writeValueAsString(maps);
        MessageDigest md5 = MessageDigest.getInstance("md5");
        md5.update(json.getBytes(StandardCharsets.UTF_8));
        byte[] bytes = md5.digest();
        md5.reset();
        //bytes数组转16进制字符串
        return Hex.encodeHexString(bytes);
    }

    //获取客户端IP，实际上这里可以直接用UserId替代
    public String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
                // 根据网卡取本机配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    log.error("根据网卡获取本机配置的IP异常", e);
                }

            }
        }

        // 对于通过多个代理的情况，第一个IP为客户端真实IP，多个IP按照','分割
        if (ipAddress != null && ipAddress.indexOf(",") > 0) {
            ipAddress = ipAddress.split(",")[0];
        }

        return ipAddress;
    }
}
